package com.yb0os1.lock;

import org.w3c.dom.Node;

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport;

public class Yb0os1Lock {
    private final boolean isFair;//是否公平锁
    private final AtomicInteger state = new AtomicInteger(0);// 锁标识
    private Thread owner = null;//锁的拥有者
    private final AtomicReference<Node> head = new AtomicReference<>(new Node());//队列头结点
    private final AtomicReference<Node> tail = new AtomicReference<>(head.get());//队列头结点


    public Yb0os1Lock() {//默认非公平锁
        this.isFair = false;
    }

    public Yb0os1Lock(boolean isFair) {//可以传递是否 公平锁
        this.isFair = isFair;
    }

    public void lock() {
        if (!isFair&& state.get() == 0) {//没有加锁 可以 直接获取锁
            if (state.compareAndSet(0, 1)) {
                this.owner = Thread.currentThread();
                System.out.println("线程" + owner.getName() + "直接拿到获取锁");
                return;
            }
        } else {//尝试冲入
            if (owner == Thread.currentThread()) {
                System.out.println("线程" + owner.getName() + "重入次数为：" + state.incrementAndGet());
                return;
            }
        }
        //下面就是公平锁逻辑和非公平没有抢到锁的线程的逻辑
        //没有抢到锁的以及公平锁，需要加入等待队列  下面的代码可能会出现线程安全问题 要格外的注意
        Node current = new Node(Thread.currentThread());
        //h --> A
        //如果线程1和线程2 同时获取currentTail为A，线程1修改尾节点成功，变成 h --> A --> B
        //此时线程2继续修改，(A,C)肯定是失败的，那么重新回去尾节点 ，然后尝试修改 变为 h --> A --> B --> C
        while (true) {
            Node currentTail = tail.get();
            if (tail.compareAndSet(currentTail, current)) {//这里对尾节点修改成功的只可能有一个线程 所以if里面是线程安全的
                System.out.println("线程" + current.thread.getName() + "加入链表尾");
                currentTail.next = current;
                current.prev = currentTail;
                break;
            }
        }
        //下面要阻塞等待 但是不能直接LockSupport.park() 因为存在虚假唤醒，所以需要一些条件才能返回
        // 所满足的条件 当前节点是head的下一个 并且 可以获取锁
        while (true) {
            //需要先自己尝试唤醒 然后再阻塞等待 在公平锁的场景下如果直接阻塞等待 就一直阻塞等待唤醒了
            // 还有一种情况 两个线程1 2  1先抢到锁 2加到链表中但是此时还没有将链表和head连接起来 线程1就释放锁但是next==null 那么线程2就一直阻塞了
            // LockSupport.park();
            if (current.prev == head.get() && state.compareAndSet(0, 1)) {//里面是线程安全的
                //需要将head后移
                current.prev.next = null;
                current.prev = null;
                this.owner = Thread.currentThread();
                System.out.println("线程" + owner.getName() + "被唤醒后拿到锁");
                head.set(current);//可以直接set不适用CAS 因为获取锁成功了 一定是线程安全的
                return;
            }
            LockSupport.park();
        }
    }

    public void unlock() {
        //解锁判断 首先是判断是否是当前线程的锁
        if (Thread.currentThread() != owner) {
            throw new IllegalStateException("当前线程不是锁的拥有者,不可以解锁");
        }
        int count = state.get();
        if(count>1){
            state.set(count-1);
            System.out.println( "线程" + owner.getName() + "减少一次重入次数：" + state.get());
            return;
        }
        if (count<=0){
            throw new IllegalStateException("重入锁解锁错误！");
        }
        //count==1
        //还需要唤醒等待队列中head.next这个线程
        Node next = head.get().next;
        //owner是当前线程，可以解锁  并且一定是线程安全的
        this.owner = null;//不把 owner 置 null，会在以下情况出问题：线程A解了锁，然后线程B秒抢锁，但还没来得及把 owner 改成B，就被 A 重入了，然后B才执行 owner = B；这时候 A，B 都有锁，A执行 unlock 的时候才发现锁是B的，就抛异常了
        state.set(0);//释放锁之后保证不了线程安全了
        if (next != null) {
            System.out.println("线程" + Thread.currentThread().getName() + "唤醒了线程" + next.thread.getName());
            LockSupport.unpark(next.thread);
        }
    }


    private class Node {
        Node next;
        Node prev;
        Thread thread;

        public Node() {
        }

        public Node(Thread thread) {
            this.thread = thread;
        }
    }
}
